﻿/*
 * Copyright (c) 2015-2016 Constantijn Evenhuis (blueprints@const.nl)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
using Blueprints.Editing;
using Blueprints.IO;
using Blueprints.Resolution;
using Blueprints.Validation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace Blueprints.Defaults
{
    internal sealed partial class DefaultValidationContext : DeserializationContext<IValidatedNode>
    {
        private Dictionary<ulong, string> booleanValues;

        public DefaultValidationContext(ITypeProvider typeProvider, Type rootType)
        : base(rootType)
        {
            Initialize(typeProvider, rootType);
        }

        public DefaultValidationContext(ITypeProvider typeProvider, ITypeResolver typeResolver, IBlueprintResolver blueprintResolver)
        : base(typeResolver, blueprintResolver)
        {
            Initialize(typeProvider, typeof(object));
        }

        public TypeDiscoverer TypeDiscoverer
        {
            get;
            private set;
        }

        public EnumDiscoverer EnumDiscoverer
        {
            get;
            private set;
        }

        internal bool TryGetValues(IValueDescriptor descriptor, out EditType editType, out IReadOnlyDictionary<ulong, string> availableValues)
        {
            Type type = descriptor.Type;
            if (type == typeof(bool))
            {
                editType = EditType.Boolean;
                availableValues = booleanValues;
                return true;
            }

            TypeInfo typeInfo = type.GetTypeInfo();
            if (typeInfo.IsEnum)
            {
                availableValues = GetEnumValues(descriptor, out editType);
                return true;
            }

            availableValues = null;
            editType = EditType.Unknown;
            return false;
        }

        internal bool TryGetValues(IValueDescriptor descriptor, string content, out EditType editType, out DeserializationResult result, out string value, out IReadOnlyDictionary<ulong, string> availableValues)
        {
            Type type = descriptor.Type;
            if (type == typeof(bool))
            {
                editType = EditType.Boolean;
                availableValues = booleanValues;
                result = GetBooleanResult(descriptor, content, out value);
                return true;
            }

            TypeInfo typeInfo = type.GetTypeInfo();
            if (typeInfo.IsEnum)
            {
                availableValues = GetEnumValues(descriptor, out editType);
                result = GetEnumResult(descriptor, content, out value);
                return true;
            }

            result = DeserializationResult.Unknown;
            value = null;
            availableValues = null;
            editType = EditType.Unknown;
            return false;
        }

        internal void SetProperties(ValidatedNode @object, IEnumerable<IValidatedNode> values)
        {
            foreach (ValidatedNode value in values)
            {
                if (value == null)
                {
                    continue;
                }

                IValidatedProperty property;
                if (!@object.Properties.TryGetValue(value.Name, out property))
                {
                    continue;
                }

                if (@object.Result == DeserializationResult.Success && value.Result != DeserializationResult.Success)
                {
                    @object.Result = DeserializationResult.DescendantFailed;
                }

            ((ValidatedProperty)property).Node = value;
            }
        }

        protected override IValidatedNode CreateArray(NodeInfo node, Type expectedType, IArrayDescriptor descriptor, IReadOnlyList<IValidatedNode> elements)
        {
            ValidatedNode validation = CreateValidatedNode(node, expectedType, descriptor.Type, EditType.Collection, DeserializationResult.Success);
            SetElements(validation, elements);
            return validation;
        }

        protected override IValidatedNode CreateCollection(NodeInfo node, Type expectedType, ICollectionDescriptor descriptor)
        {
            return CreateValidatedNode(node, expectedType, descriptor.Type, EditType.Collection, DeserializationResult.Success);
        }

        protected override IValidatedNode CreateIncompleteObject(NodeInfo node, Type expectedType, IObjectDescriptor descriptor, IValidatedNode[] parameters, IReadOnlyDictionary<IPropertyDescriptor, IValidatedNode> properties)
        {
            ValidatedNode validation = CreateValidatedNode(node, expectedType, descriptor.Type, EditType.Object, DeserializationResult.MissingRequiredProperty);
            validation.Properties = CreateProperties(validation, descriptor);
            validation.AvailableTypes = GetCompatibleTypes(expectedType);
            SetProperties(validation, parameters);
            SetProperties(validation, properties.Values);
            return validation;
        }

        protected override IValidatedNode CreateInvalidValue(NodeInfo node, Type expectedType, IValueDescriptor descriptor, string content)
        {
            DeserializationResult result;
            EditType editType;
            string value;
            IReadOnlyDictionary<ulong, string> availableValues;
            if (!TryGetValues(descriptor, content, out editType, out result, out value, out availableValues))
            {
                result = DeserializationResult.InvalidValue;
                availableValues = new Dictionary<ulong, string>();
                value = content;
                editType = EditType.Value;
            }

            ValidatedNode validation = CreateValidatedNode(node, expectedType, descriptor.Type, editType, result);
            validation.Value = content;
            validation.AvailableValues = availableValues;
            return validation;
        }

        protected override IValidatedNode CreateLink(NodeInfo node, Type expectedType, IValidatedNode result)
        {
            return CreateValidatedNode(node, expectedType, expectedType, EditType.Link, DeserializationResult.Success);
        }

        protected override IValidatedNode CreateLinkError(NodeInfo node, Type expectedType, DeserializationResult result)
        {
            return CreateValidatedNode(node, expectedType, expectedType, EditType.Link, result);
        }

        protected override IValidatedNode CreateNull(NodeInfo node, Type expectedType)
        {
            return CreateValidatedNode(node, expectedType, null, EditType.Link, DeserializationResult.Success);
        }

        protected override IValidatedNode CreateObject(NodeInfo node, Type expectedType, IObjectDescriptor descriptor, IValidatedNode[] parameters)
        {
            ValidatedNode validation = CreateValidatedNode(node, expectedType, descriptor.Type, EditType.Object, DeserializationResult.Success);
            validation.Properties = CreateProperties(validation, descriptor);
            validation.AvailableTypes = GetCompatibleTypes(expectedType);
            SetProperties(validation, parameters);
            return validation;
        }

        protected override IValidatedNode CreateTypeError(NodeInfo node, Type expectedType, DeserializationResult result, Type actualType)
        {
            ValidatedNode validation = CreateValidatedNode(node, expectedType, null, EditType.Object, result);
            validation.AvailableTypes = GetCompatibleTypes(expectedType);
            return validation;
        }

        protected override IValidatedNode CreateValue(NodeInfo node, Type expectedType, IValueDescriptor descriptor, string content)
        {
            EditType editType;
            DeserializationResult result;
            string value;
            IReadOnlyDictionary<ulong, string> availableValues;
            if (!TryGetValues(descriptor, content, out editType, out result, out value, out availableValues))
            {
                result = DeserializationResult.Success;
                availableValues = new Dictionary<ulong, string>();
                value = content;
                editType = EditType.Value;
            }

            ValidatedNode validation = CreateValidatedNode(node, expectedType, descriptor.Type, editType, result);
            if (editType == EditType.MultiSelect)
            {
                value = Convert.ToUInt64(descriptor.ConvertFromString(value)).ToString();
            }

            validation.Value = value;
            validation.AvailableValues = availableValues;
            return validation;
        }

        protected override void SetCollectionElements(IValidatedNode validation, ICollectionDescriptor descriptor, IEnumerable<IValidatedNode> elements)
        {
            SetElements((ValidatedNode)validation, elements);
        }

        protected override void SetObjectProperties(IValidatedNode validation, IReadOnlyDictionary<IPropertyDescriptor, IValidatedNode> properties)
        {
            SetProperties((ValidatedNode)validation, properties.Values);
        }

        private void Initialize(ITypeProvider typeProvider, Type rootType)
        {
            TypeDiscoverer = new TypeDiscoverer(typeProvider, rootType);
            EnumDiscoverer = new EnumDiscoverer();

            IValueDescriptor booleanDescriptor = TypeResolver.GetValueDescriptor(typeof(bool));
            booleanValues = new Dictionary<ulong, string>
{
{ 0, booleanDescriptor.ConvertToString(false) },
{ 1, booleanDescriptor.ConvertToString(true) },
};
        }

        private DeserializationResult GetBooleanResult(IValueDescriptor descriptor, string content, out string value)
        {
            try
            {
                bool @boolean = (bool)descriptor.ConvertFromString(content);
                value = booleanValues[(ulong)(@boolean ? 1 : 0)];
                return DeserializationResult.Success;
            }
            catch
            {
                value = null;
                return DeserializationResult.InvalidValue;
            }
        }

        private IReadOnlyDictionary<ulong, string> GetEnumValues(IValueDescriptor descriptor, out EditType editType)
        {
            if (descriptor.Type.GetTypeInfo().IsDefined(typeof(FlagsAttribute)))
            {
                editType = EditType.MultiSelect;
            }
            else
            {
                editType = EditType.SingleSelect;
            }

            return EnumDiscoverer.GetValues(descriptor.Type);
        }

        private DeserializationResult GetEnumResult(IValueDescriptor descriptor, string content, out string value)
        {
            try
            {
                object enumValue = descriptor.ConvertFromString(content);
                ulong @ulong = EnumDiscoverer.GetValue(descriptor.Type, enumValue);
                value = descriptor.ConvertToString(@ulong);
                return DeserializationResult.Success;
            }
            catch
            {
                value = null;
                return DeserializationResult.InvalidValue;
            }
        }

        private ValidatedNode CreateValidatedNode(NodeInfo nodeInfo, Type expectedType, Type actualType, EditType editType, DeserializationResult result)
        {
            string type = actualType == null ? nodeInfo.Node.Type : TypeResolver.GetTypeName(actualType);
            return new ValidatedNode(nodeInfo.Node, nodeInfo.Node.Name, nodeInfo.Path, type, editType, result);
        }

        private ValidatedProperty CreateValidatedProperty(Type declaringType, IPropertyDescriptor descriptor, bool isRequired)
        {
            string type = TypeResolver.GetTypeName(descriptor.Type);

            EditType editType;
            IReadOnlyDictionary<ulong, string> availableValues;
            TypeCategory typeCategory = TypeResolver.GetTypeCategory(descriptor.Type);
            if (typeCategory == TypeCategory.Value)
            {
                if (!TryGetValues(TypeResolver.GetValueDescriptor(descriptor.Type), out editType, out availableValues))
                {
                    availableValues = new Dictionary<ulong, string>();
                    editType = EditType.Value;
                }
            }
            else
            {
                availableValues = new Dictionary<ulong, string>();
                switch (typeCategory)
                {
                    case TypeCategory.Array:
                    case TypeCategory.Collection:
                        editType = EditType.Collection;
                        break;
                    default:
                        editType = EditType.Object;
                        break;
                }
            }

            return new ValidatedProperty(descriptor.Name, isRequired, type, editType, availableValues);
        }

        private IReadOnlyDictionary<string, IValidatedProperty> CreateProperties(ValidatedNode node, IObjectDescriptor descriptor)
        {
            Dictionary<string, IValidatedProperty> properties = new Dictionary<string, IValidatedProperty>(StringComparer.Ordinal);
            foreach (IPropertyDescriptor property in descriptor.OptionalProperties)
            {
                properties[property.Name] = CreateValidatedProperty(descriptor.Type, property, false);
            }

            foreach (IPropertyDescriptor parameter in descriptor.RequiredProperties)
            {
                properties[parameter.Name] = CreateValidatedProperty(descriptor.Type, parameter, true);
            }

            return properties;
        }

        private IReadOnlyList<string> GetCompatibleTypes(Type expectedType)
        {
            IReadOnlyList<Type> compatibleTypes = TypeDiscoverer.GetCompatibleTypes(expectedType);
            List<string> availableTypes = new List<string>(compatibleTypes.Count);
            foreach (Type availableType in compatibleTypes)
            {
                availableTypes.Add(TypeResolver.GetTypeName(availableType));
            }

            return availableTypes;
        }

        private void SetElements(ValidatedNode collection, IEnumerable<IValidatedNode> values)
        {
            List<IValidatedNode> elements = new List<IValidatedNode>();
            foreach (IValidatedNode value in values)
            {
                if (collection.Result == DeserializationResult.Success && value.Result != DeserializationResult.Success)
                {
                    collection.Result = DeserializationResult.DescendantFailed;
                }

                elements.Add(value);
            }

            collection.Elements = elements;
        }
    }
}